package ep.logic;

import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import ep.io.BoardAntPrinter;
import ep.model.Ant;
import ep.model.Board;

public class GodGubi {

	private Board actualBoard;

	private Board nextBoard;

	private Ant z;

	private int iteractions;

	private int numberOfProcessors;

	private Lock lock = new ReentrantLock();

	private Condition tasksCondition = lock.newCondition();

	private Condition mainCondition = lock.newCondition();

	private Collection<Task> tasks = new HashSet<Task>();

	private volatile int numberTasksWait = 0;

	private volatile boolean mainWait;

	private class Task implements Runnable {

		private int initialLine;

		private int lastLine;

		private volatile boolean done = false;

		public Task(int initialLine, int lastLine) {
			super();
			this.initialLine = initialLine;
			this.lastLine = lastLine;
		}

		public void run() {
			while (!this.done) {
				try {
					GodGubi.this.lock.lock();
					if (GodGubi.this.numberTasksWait == GodGubi.this.numberOfProcessors - 1
							&& GodGubi.this.mainWait) {
						GodGubi.this.mainWait = false;
						GodGubi.this.mainCondition.signal();
					}
					GodGubi.this.numberTasksWait++;
					GodGubi.this.tasksCondition.await();
				} catch (InterruptedException e) {
					System.err.println("Alguem me acordou");
					e.printStackTrace();
				} finally {
					GodGubi.this.lock.unlock();
				}

				this.processLifeRules();
			}
		}

		private void processLifeRules() {

			for (int i = this.initialLine; i <= this.lastLine; i++) {
				for (int j = 1; j < GodGubi.this.actualBoard.getWidth() - 1; j++) {
					int numberOfNeighborOn = this.calculateNumberOfNeighborOn(
							i, j);

					GodGubi.this.nextBoard.setPositionState(i, j,
							GodGubi.this.actualBoard.getPositionState(i, j));

					if (this.isCloseToAnt(i, j)) {
						continue;
					}

					// solidão ou fome
					if (numberOfNeighborOn < 2 || numberOfNeighborOn > 3) {
						GodGubi.this.nextBoard.turnOff(i, j);
					} else if (numberOfNeighborOn == 3) { // nascimento
						GodGubi.this.nextBoard.turnOn(i, j);
					}
				}
			}

		}

		private boolean isCloseToAnt(int i, int j) {
			int di1 = Math.abs(i - GodGubi.this.z.getI());
			int dj1 = Math.abs(j - GodGubi.this.z.getJ());

			int di2 = GodGubi.this.actualBoard.getHeight() - di1 - 2;
			int dj2 = GodGubi.this.actualBoard.getWidth() - dj1 - 2;

			int di = di1 > di2 ? di2 : di1;
			int dj = dj1 > dj2 ? dj2 : dj1;

			return (di + dj) <= 3;
		}

		private int calculateNumberOfNeighborOn(int i, int j) {
			int count = 0;

			count += GodGubi.this.actualBoard.getPositionState(i, j - 1);
			count += GodGubi.this.actualBoard.getPositionState(i - 1, j - 1);
			count += GodGubi.this.actualBoard.getPositionState(i - 1, j);
			count += GodGubi.this.actualBoard.getPositionState(i - 1, j + 1);
			count += GodGubi.this.actualBoard.getPositionState(i, j + 1);
			count += GodGubi.this.actualBoard.getPositionState(i + 1, j + 1);
			count += GodGubi.this.actualBoard.getPositionState(i + 1, j);
			count += GodGubi.this.actualBoard.getPositionState(i + 1, j - 1);

			return count;
		}

		public void setDone(boolean done) {
			this.done = done;
		}
	}

	public GodGubi(Board initialBoard, Ant z, int iteractions,
			int numberOfProcessors) {
		super();
		this.actualBoard = initialBoard;
		this.z = z;
		this.iteractions = iteractions;
		this.numberOfProcessors = numberOfProcessors;
	}

	public void playGame() {
		this.createThreads();

		for (int i = 1; i <= this.iteractions; i++) {
			this.nextBoard = new Board(this.actualBoard.getHeight(),
					this.actualBoard.getWidth());
			this.runLife();
			this.runAnt();
			this.actualBoard = this.nextBoard;
			// TODO imprimindo para testar
			//new BoardAntPrinter().print(this.actualBoard, this.z);
		}
		
		// TODO imprimindo para testar
		new BoardAntPrinter().print(this.actualBoard, this.z);
		
		for (Task task : this.tasks) {
			task.setDone(true);
		}

		this.wakeUpTasks();
	}

	private void runLife() {
		this.wakeUpTasks();
		this.waitTasks();
	}

	private void runAnt() {
		if (this.nextBoard.getPositionState(this.z.getI(), this.z.getJ()) == 0) {
			this.z.turnCCW();
			this.nextBoard.setPositionState(this.z.getI(), this.z.getJ(), 1);
		} else {
			this.z.turnCW();
			this.nextBoard.setPositionState(this.z.getI(), this.z.getJ(), 0);
		}
		this.z.doOneStep();

		this.dontLetTheAntRunaway();
	}

	private void createThreads() {
		int chunkSize = (this.actualBoard.getHeight() - 2)
				/ this.numberOfProcessors;

		// Do primeiro até o penúltimo processador
		for (int i = 0; i < this.numberOfProcessors - 1; i++) {
			int initialLine = i * chunkSize + 1;
			int lastLine = (i + 1) * chunkSize;
			Task task = new Task(initialLine, lastLine);
			this.tasks.add(task);
			new Thread(task).start();
		}

		// último processador
		int initialLine = (this.numberOfProcessors - 1) * chunkSize + 1;
		int lastLine = this.actualBoard.getHeight() - 2;
		Task task = new Task(initialLine, lastLine);
		this.tasks.add(task);
		new Thread(task).start();

		this.waitTasks();
	}

	private void dontLetTheAntRunaway() {
		if (this.z.getI() == 0) {
			this.z.setI(this.actualBoard.getHeight() - 2);
		} else if (this.z.getI() == this.actualBoard.getHeight() - 1) {
			this.z.setI(1);
		} else if (this.z.getJ() == 0) {
			this.z.setJ(this.actualBoard.getWidth() - 2);
		} else if (this.z.getJ() == this.actualBoard.getWidth() - 1) {
			this.z.setJ(1);
		}
	}

	private void waitTasks() {
		try {
			this.lock.lock();
			if (this.numberTasksWait != this.numberOfProcessors) {
				this.mainWait = true;
				this.mainCondition.await();
			}
		} catch (InterruptedException e) {
			System.err.println("Alguem me acordou");
			e.printStackTrace();
		} finally {
			this.lock.unlock();
		}
	}

	private void wakeUpTasks() {
		try {
			this.lock.lock();
			this.numberTasksWait = 0;
			this.tasksCondition.signalAll();
		} finally {
			this.lock.unlock();
		}
	}
}
